<?php
declare (strict_types=1);

namespace app\exam\logic;

use app\common\exception\LogicException;
use app\common\logic\category\Category;
use app\common\model\ProductType;
use app\exam\model\Special as SpecialModel;
use think\facade\{Cache};
use think\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\Model;
use think\Paginator;

/**
 * 专项练习逻辑
 */
class Special extends SpecialModel
{

    /**
     * 获取专项练习列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-03
     *
     * @param  [type] $param [description]
     * @return mixed [type]        [description]
     * @throws DbException
     * @throws DataNotFoundException
     * @throws ModelNotFoundException
     */
    public function getSpecialList($param)
    {
        // 验证数据
        $this->getValidate()->scene('special_list')->failException()->check($param);
        // 参数
        $moduleId = $param['module_id'];
        $category = $param['category'] ?? 0;
//        if (!$category) {
//            /** @var Category $cateLogic */
//            $cateLogic = app(Category::class);
//            $category  = $cateLogic->getFirstTopId(3);
//        }
        // 专项训练树
        $tree = $this->getSpecialTree($moduleId, 0, $category);
        // 解析专项每级包含的试题数量
        $tree = $this->parseTreeTopicCount($tree);
        // 解析专项每级试题作答数量
        $tree = $this->parseTreeTopicAnsweredNum($tree);
        // 处理专项每级试题作答最后作答试题
        return $this->parseTreeTopicLastPractice($tree);
    }

    /**
     * 搜索专项列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-19
     *
     * @param  [type] $param [description]
     * @return Paginator [type]        [description]
     * @throws DbException
     */
    public function search($param)
    {
        // 验证数据
        $this->getValidate()->scene('special_search')->failException()->check($param);
        // 参数
        $moduleId = $param['module_id'];
        $category = $param['category'];
        $title    = $param['title'];
        $data = $this->field('id,pid')->whereLike('title', '%' . $title . '%')->paginate($this->getListRows());

        if ($data->isEmpty()) return $data;

        $ids = [];

        foreach ($data as $item) {
            $ids[] = $this->getFirstParentId($item);
        }
        // 参数：专项顶级ID集，用于搜索接口
        $ids = array_unique($ids);
        // 专项训练树
        $tree = $this->getSpecialTree($moduleId, 0, $category, $ids);
        // 解析专项每级包含的试题数量
        $tree = $this->parseTreeTopicCount($tree);
        // 解析专项每级试题作答数量
        $tree = $this->parseTreeTopicAnsweredNum($tree);
        // 处理专项每级试题作答最后作答试题
        return $this->parseTreeTopicLastPractice($tree);
    }

    /**
     * 专项训练树
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param integer $moduleId 版块ID
     * @param integer $pid      专项父级ID
     * @param int     $category
     * @param array   $ids      专项顶级ID集，用于搜索接口
     * @return Special[]|array|Collection [type]             [description]
     * @throws DbException
     * @throws DataNotFoundException
     * @throws ModelNotFoundException
     */
    public function getSpecialTree($moduleId = 0, $pid = 0, $category = 0, $ids = [])
    {
        $field   = 'id,pid,title,level,topic_count,price';
        $map     = [
            ['pid', '=', $pid],
//            ['category', 'like', '%' . $moduleId . ',%'],
//            ['module_id', '=', 5],
            ['topic_count', '<>', 0],
//            ['category', 'like', '%' . $category . ',%']
        ];
        if (!empty($moduleId)){
            $map[]= ['category', 'like', '%' . $moduleId . ',%'];
        }
        if (!empty($category)){
            $map[]= ['category', 'like', '%' . $category . ',%'];
        }
//        halt($map);
        $builder = $this->field($field);
        // $builder = $category ? $this->whereRaw("json_contains(fullcategorypath, '" . $category . "')") : $this;
        if (0 == $pid) {
            count($ids) && $map[] = ['id', 'IN', $ids];
            $data = $builder->with(['accuracy' => function ($query) {
                $query->where(['user_id' => request()->mid])->order('create_time', 'DESC');
            }])->withCount(['has_bought' => 'has_bought'])->where($map)->paginate($this->getListRows())->hidden(['status', 'create_time', 'update_time', 'delete_time']);
        } else {
            $data = $builder->with(['accuracy' => function ($query) {
                $query->where(['user_id' => request()->mid])->order('create_time', 'DESC');
            }])->where($map)->select();
        }
        if (!$data->isEmpty()) {
            foreach ($data as $val) {
                if ($this->hasChildren($val->id)) {
                    $val->answered_num = 0;
                    $val->child        = $this->getSpecialTree($moduleId, $val->id, $category, $ids);
                } else {
                    $answered_num      = $this->getSpecialAnsweredNum(intval($val->id));
                    $val->answered_num = $answered_num;
                }
                $val->first_topic        = $this->getSpecialFirstTopicId($val->id);
                $last_practice           = $this->getLastPracticeTopic(intval($val->id));
                $val->last_practice      = $last_practice ? $last_practice['topic_id'] : 0;
                $val->last_practice_time = $last_practice ? $last_practice['answer_time'] : 0;
                $val->accuracy           = $val->accuracy ?? 0;
                $val->pid                = $val->pid > 0 ? $val->pid : $val->id;
            }
        }

        return $data;
    }

    /**
     * 获取练习缓存数据
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-01
     *
     * @param int $special_id
     * @return mixed|null [type]        [description]
     */
    public function getPracticeRedis(int $special_id)
    {
        $redis = Cache::store('redis')->handler();
        $data  = $redis->hGet('special_topic_practice', request()->mid . '_' . $special_id);

        return $data ? unserialize($data) : null;
    }

    /**
     * 专项最后答题试题ID
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-01
     *
     * @param int $special_id [description]
     * @return array|false|mixed [type]             [description]
     */
    public function getLastPracticeTopic(int $special_id)
    {
        $redis = $this->getPracticeRedis($special_id);
        if ($redis) return end($redis);
        return [];
    }

    /**
     * 获取末级子项已答题数
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-02
     *
     * @param int $special_id [description]
     * @return int [type]             [description]
     */
    public function getSpecialAnsweredNum(int $special_id)
    {
        $redis = $this->getPracticeRedis($special_id);
        return $redis ? count($redis) : 0;
    }

    /**
     * 获取顶级父级ID
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-19
     *
     * @param  [type] $item [description]
     * @return mixed [type]       [description]
     */
    public function getFirstParentId($item)
    {
        if ($item->pid == 0) return $item->id;

        $data = $this->field('id,pid,level')->whereId($item->pid)->findOrEmpty();
        if ($data->pid != 0) {    // $data->level != 1
            return $this->getFirstParentId($data);
        }

        return $data->id;
    }

    /**
     * 根据专项ID获取顶级专项名称
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-10
     */
    public function getTopTitleById($id)
    {
        $data = $this->field('id,pid,level,title')->whereId($id)->findOrEmpty();
        if ($data->pid) {
            return $this->getTopTitleById($data->pid);
        }
        return $data->title;
    }

    /**
     * 是否有子集
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param int $pid [description]
     * @return boolean          [description]
     */
    public function hasChildren(int $pid = 0): bool
    {
        return !!$this->where('pid', $pid)->count();
    }

    /**
     * 解析专项每级包含的试题数量
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param  [type] $tree [description]
     * @return mixed [type]       [description]
     */
    public function parseTreeTopicCount($tree)
    {
        foreach ($tree as $v) {
            if (isset($v->child)) {
                $count = $this->computeCount($v->child);
                if ($v->topic_count != $count) {
                    $v->topic_count = $count;
                    $v->save();
                }
                $this->parseTreeTopicCount($v->child);
            }
        }
        return $tree;
    }

    /**
     * 计算当级包含的试题数量
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param  [type]  $tree  [description]
     * @param integer $count [description]
     * @return int [type]         [description]
     */
    public function computeCount($tree, $count = 0)
    {
        foreach ($tree as $v) {
            if (isset($v->child)) {
                $count = $this->computeCount($v->child, $count);
            } else {
                $count += $v->topic_count;
            }
        }
        return $count;
    }

    /**
     * 解析专项每级试题作答数量
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param  [type] $tree [description]
     * @return mixed [type]       [description]
     */
    public function parseTreeTopicAnsweredNum($tree)
    {
        foreach ($tree as $v) {
            if (isset($v->child)) {
                $answered_num = $this->computeAnsweredNum($v->child);
                if ($v->answered_num != $answered_num) {
                    $v->answered_num = $answered_num;
                }
                $this->parseTreeTopicAnsweredNum($v->child);
            }
        }
        return $tree;
    }

    /**
     * 计算当级试题作答数量
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param  [type]  $tree  [description]
     * @param int $num
     * @return int|mixed [type]         [description]
     */
    public function computeAnsweredNum($tree, $num = 0)
    {
        foreach ($tree as $v) {
            if (isset($v->child)) {
                $num = $this->computeAnsweredNum($v->child, $num);
            } else {
                $num += $v->answered_num;
            }
        }
        return $num;
    }

    /**
     * 处理专项每级试题作答最后作答试题
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-03
     *
     * @param  [type] $tree [description]
     * @return mixed [type]       [description]
     */
    public function parseTreeTopicLastPractice($tree)
    {
        foreach ($tree as $special) {
            $this->parseSpecialLastPractice($special);
        }
        return $tree;
    }

    /**
     * 处理专项每级试题作答最后作答试题（最后原则）
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-03
     *
     * @param  [type] $tree [description]
     * @return void [type]       [description]
     */
    public function parseSpecialLastPractice($special)
    {
        if (isset($special->child)) {
            $last_topic = $special->last_practice;
            $last_time  = $special->last_practice_time;
            foreach ($special->child as $child) {
                if (isset($child->child)) $this->parseSpecialLastPractice($child);
                if ($child->last_practice && $last_time < $child->last_practice_time) {
                    $last_topic = $child->last_practice;
                    $last_time  = $child->last_practice_time;
                }
            }
            $special->last_practice      = $last_topic;
            $special->last_practice_time = $last_time;
        }
    }

    /**
     * 处理专项每级试题作答最后作答试题（正序处理）
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-03
     *
     * @param  [type] $tree [description]
     * @return void [type]       [description]
     */
    public function parseSpecialLastPractice1($special)
    {
        if (isset($special->child)) {
            $last_practice = $special->last_practice;
            foreach ($special->child as $child) {
                if (isset($child->child)) $this->parseSpecialLastPractice($child);
                if (!$child->last_practice) break;
                $last_practice = $child->last_practice;
                if ($child->answer_num != $child->topic_count) break;
            }
            $special->last_practice = $last_practice;
        }
    }

    /**
     * 获取专项基础信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-03
     *
     * @param  [type] $param [description]
     * @return array [type]        [description]
     */
    public function getSpecialBase($param)
    {
        // 验证数据
        $this->getValidate()->scene('special_base')->failException()->check($param);
        // 参数
        $special_id = intval($param['special_id']);
        //
        // $title = $this->getTopTitleById($special_id);
        $title = $this->getTitleById($special_id);
        // 获取所有末级id
        $last_ids = $this->getSpecialLastIds($special_id);

        $data          = app(SpecialTopic::class)->getSpecialTopicBase($last_ids);
        $data['title'] = $title;

        return $data;
    }

    /**
     * 获取专项第一末级ID
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-01
     *
     * @param int $special_id [description]
     * @return int|null [type]             [description]
     */
    public function getSpecialCurrentId(int $special_id)
    {
        if (!$special_id) return null;
        $id = $this->wherePid($special_id)->order('id')->value('id');
        if ($id) {
            return $this->getSpecialCurrentId(intval($id));
        } else {
            return $special_id;
        }
    }

    /**
     * 获取指定专项第一个试题ID
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-01
     *
     * @param int $special_id [description]
     * @return int [type]             [description]
     */
    public function getSpecialFirstTopicId(int $special_id = 0)
    {
        $ids = $this->getSpecialLastIds($special_id);
        if ($ids) {
            $topic_id = app(SpecialTopic::class)->alias('s')
                ->join('exam_question_topic t', "s.topic_id=t.id")
                ->where('special_id', 'IN', $ids)
                ->order('t.question_type,t.id')
                ->value('topic_id');
            return $topic_id ?: 0;
        }
        return 0;
    }

    /**
     * 获取所有末级id集合
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-04
     *
     * @param int   $special_id
     * @param array $ids
     * @return array|mixed [type]             [description]
     */
    public function getSpecialLastIds(int $special_id, $ids = [])
    {
        $next = $this->wherePid($special_id)->select();

        if ($next->isEmpty()) {
            if (!in_array($special_id, $ids)) $ids[] = $special_id;
        } else {
            foreach ($next as $n) {
                if (!in_array($n->id, $ids))  $ids[] = $n->id;
                $ids = $this->getSpecialLastIds($n->id, $ids);
            }
        }
        return $ids;
    }

    /**
     * 获取试题信息及选项
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-05
     *
     * @param  [type] $param [description]
     * @return array [type]        [description]
     */
    public function getSpecialTopic($param)
    {
        // 数据验证
        $this->getValidate()->scene('special_topic')->failException()->check($param);
        // 参数
        $topic_id = $param['topic_id'];

        return app(Topic::class)->getTopicInfoById($topic_id);
    }

    /**
     * 提交专项练习试题答案
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-05
     *
     * @param $param
     * @return void [type] [description]
     */
    public function postSpecialAnswer($param)
    {
        // 数据验证
        $this->getValidate()->scene('special_answer')->failException()->check($param);
        // 参数
        $special_id = intval($param['special_id']);
        $topic_id   = intval($param['topic_id']);
        $is_right   = intval($param['is_right']);
        $answer     = is_array($param['answer']) ? $param['answer'] : json_decode($param['answer'], true);
        // 新参数
        $parent_id      = intval($param['parent_id']);
        $special_code   = intval($param['special_code']);
        if ($special_code == 0) throw new LogicException("唯一码错误");

        $yes        = app(SpecialTopic::class)->whereSpecialId($special_id)->whereTopicId($topic_id)->findOrEmpty();
        if ($yes->isEmpty()) throw new LogicException("该专项不存在此试题");

        app(TopicPracticeRecord::class)->addRecord(1, $topic_id, $is_right, $special_id, $answer, $parent_id, $special_code);
    }

    /**
     * 获取专项名称
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-01
     *
     * @param  [type] $id [description]
     * @return mixed [type]     [description]
     */
    public function getTitleById($id)
    {
        return $this->whereId($id)->value('title');
    }

    /**
     * 订单产品详情
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-02
     *
     * @param  [type] $orderItem [description]
     * @return Special|array|Model [type]            [description]
     */
    public function getProductInfo($orderItem)
    {
        $info              = $this->field('id,title,price,module_id')->with(['mhm_id'])->findOrEmpty($orderItem->product_id)->hidden(['module_id']);
        $info->type_id     = ProductType::getProductIdByKey('special');
        $info->payment     = $orderItem->payment;
        $info->expire_time = $orderItem->expire_time;
        $info->expire_rest = $orderItem->expire_time ? ceil(max(0, $orderItem->expire_time - time()) / 86400) : -1;

        return $info;
    }

    /**
     * 清空专项记录
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-04
     *
     * @param array $param
     * @return void [type]        [description]
     */
    public function clearSpecialRecord(array $param): void
    {
        // 数据验证
        $this->getValidate()->scene('clear')->failException()->check($param);
        // 参数
        $special_id = intval($param['special_id']);
        $ids        = $this->getSpecialLastIds($special_id);

        $redis = Cache::store('redis')->handler();
        foreach ($ids as $id) {
            $redis->hDel('special_topic_practice', request()->mid . '_' . $id);
        }
    }

}
